package br.usp.ime.origami.foldcut.structures.winged;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import br.usp.ime.origami.foldcut.gui.util.Figure;
import br.usp.ime.origami.foldcut.gui.util.OrigamiShape;
import br.usp.ime.origami.foldcut.structures.simplegraph.Edge;
import br.usp.ime.origami.foldcut.structures.simplegraph.SimpleEdge;
import br.usp.ime.origami.foldcut.structures.simplegraph.Graph;
import br.usp.ime.origami.foldcut.structures.simplegraph.Vertex;
import br.usp.ime.origami.primitives.AnglePrimitives;
import br.usp.ime.origami.view.Drawable;

public class WingedGraph implements Drawable
{
	private Set<WingedFace> wingedFaces = new HashSet<WingedFace>();

	private Set<WingedVertex> wingedVertices = new HashSet<WingedVertex>();

	private Set<WingedEdge> wingedEdges = new HashSet<WingedEdge>();

	private Graph graph;

	private WingedFace exertenalFace;

	public WingedGraph(Graph g)
	{
		this.graph = g;

		List<WingedEdge> edges = new ArrayList<WingedEdge>();
		for (SimpleEdge e : g.cloneEdges())
		{
			edges.add(findWingedEdge(e));
		}

		while (true)
		{
			WingedEdge we = edges.remove(0);
			WingedEdge previous, actual;

			// CCW
			if (we.getCcw() == null)
			{
				WingedFace newFace = new WingedFace(we);
				wingedFaces.add(newFace);

				previous = we;
				actual = getNextCcw(we);
				we.setCcw(newFace);
				we.setNextCcw(actual);

				boolean inverse = false;

				// Simulator.getInstance().addSimulationStep(previous);
				while (actual != we)
				{
					// Simulator.getInstance().addSimulationStep(actual);
					if (!inverse)
					{
						if (actual.getStart() == previous.getEnd())
						{
							actual.setPreviousCcw(previous);

							actual.setCcw(newFace);

							previous = actual;

							actual = getNextCcw(actual);
							previous.setNextCcw(actual);

							inverse = false;
						}
						else
						{
							actual.setNextCw(previous);

							actual.setCw(newFace);

							previous = actual;

							actual = getPreviousCw(actual);

							previous.setPreviousCw(actual);

							inverse = true;
						}
					}
					else
					{

						if (actual.getEnd() == previous.getStart())
						{
							actual.setNextCw(previous);

							actual.setCw(newFace);

							previous = actual;

							actual = getPreviousCw(actual);

							previous.setPreviousCw(actual);

							inverse = true;
						}
						else
						{
							actual.setPreviousCcw(previous);

							actual.setCcw(newFace);

							previous = actual;

							actual = getNextCcw(actual);

							previous.setNextCcw(actual);

							inverse = false;
						}
					}

				}
				we.setPreviousCcw(previous);

			}

			// CW
			if (we.getCw() == null)
			{
				WingedFace newFace = new WingedFace(we);
				wingedFaces.add(newFace);

				previous = we;
				actual = getNextCw(we);
				we.setCw(newFace);
				we.setNextCw(actual);

				boolean inverse = false;

				// Simulator.getInstance().addSimulationStep(previous);
				while (actual != we)
				{
					// Simulator.getInstance().addSimulationStep(actual);
					if (!inverse)
					{
						if (actual.getStart() == previous.getEnd())
						{
							actual.setPreviousCw(previous);

							actual.setCw(newFace);

							previous = actual;
							actual = getNextCw(actual);
							previous.setNextCw(actual);
							inverse = false;
						}
						else
						{
							actual.setNextCcw(previous);

							actual.setCcw(newFace);

							previous = actual;
							actual = getPreviousCcw(actual);
							previous.setPreviousCcw(actual);
							inverse = true;
						}
					}
					else
					{
						if (actual.getEnd() == previous.getStart())
						{
							actual.setNextCcw(previous);

							actual.setCcw(newFace);

							previous = actual;
							actual = getPreviousCcw(actual);
							previous.setPreviousCcw(actual);
							inverse = true;
						}
						else
						{
							actual.setPreviousCw(previous);

							actual.setCw(newFace);

							previous = actual;
							actual = getNextCw(actual);
							previous.setNextCw(actual);
							inverse = false;
						}
					}

				}
				// fecho a face cw
				we.setPreviousCw(previous);
			}

			if (edges.size() == 0)
				break;
		}
	}

	private WingedEdge getPreviousCw(WingedEdge we)
	{
		return getNextCcw(new WingedEdge(we.getEnd(), we.getStart()));
	}

	private WingedEdge getPreviousCcw(WingedEdge we)
	{
		return getNextCw(new WingedEdge(we.getEnd(), we.getStart()));
	}

	private WingedEdge getNextCcw(WingedEdge we)
	{
		Set<SimpleEdge> incident = getIncidentEdges(we.getEnd());

		double maxAngle = Double.MIN_VALUE;

		SimpleEdge nextCcw = null;
		for (SimpleEdge e : incident)
		{
			if (we.getEnd().equals(new WingedVertex(e.getStart()))
					&& !we.getStart().equals(new WingedVertex(e.getEnd())))
			{
				double angle = AnglePrimitives.angleBetween(we.getStart(), we.getEnd(), e.getEnd());

				if (angle > maxAngle)
				{
					nextCcw = e;
					maxAngle = angle;
				}
			}
			else if (we.getEnd().equals(new WingedVertex(e.getEnd()))
					&& !we.getStart().equals(new WingedVertex(e.getStart())))
			{
				double angle = AnglePrimitives.angleBetween(we.getStart(), we.getEnd(), e.getStart());

				if (angle > maxAngle)
				{
					nextCcw = e;
					maxAngle = angle;
				}
			}
		}

		assert nextCcw != null;

		WingedEdge edge = findWingedEdge(nextCcw);
		assert (!edge.equals(we));

		return edge;
	}

	private WingedEdge getNextCw(WingedEdge we)
	{
		Set<SimpleEdge> incident = getIncidentEdges(we.getEnd());

		double minAngle = Double.MAX_VALUE;
		SimpleEdge nextCw = null;

		for (SimpleEdge e : incident)
		{
			if (we.getEnd().equals(new WingedVertex(e.getStart()))
					&& !we.getStart().equals(new WingedVertex(e.getEnd())))
			{
				double angle = AnglePrimitives.angleBetween(we.getStart(), we.getEnd(), e.getEnd());

				if (angle < minAngle)
				{
					nextCw = e;
					minAngle = angle;
				}
			}
			else if (we.getEnd().equals(new WingedVertex(e.getEnd()))
					&& !we.getStart().equals(new WingedVertex(e.getStart())))
			{
				double angle = AnglePrimitives.angleBetween(we.getStart(), we.getEnd(), e.getStart());

				if (angle < minAngle)
				{
					nextCw = e;
					minAngle = angle;
				}
			}
		}

		assert nextCw != null;

		WingedEdge edge = findWingedEdge(nextCw);
		assert (!edge.equals(we));

		return edge;
	}

	private Set<SimpleEdge> getIncidentEdges(WingedVertex end)
	{
		Set<SimpleEdge> incident = new HashSet<SimpleEdge>();
		Set<SimpleEdge> edges = new HashSet<SimpleEdge>(graph.cloneEdges());
		for (Edge e : edges)
		{
			if (new WingedVertex(e.getStart()).equals(end))
			{
				incident.add(new SimpleEdge(e.getStart(), e.getEnd()));
			}
			if (new WingedVertex(e.getEnd()).equals(end))
			{
				incident.add(new SimpleEdge(e.getEnd(), e.getStart()));
			}
		}

		return incident;
	}

	private WingedEdge findWingedEdge(SimpleEdge edge)
	{
		WingedVertex i = findWingedVertex(edge.getStart());
		WingedVertex j = findWingedVertex(edge.getEnd());

		WingedEdge candidate = new WingedEdge(i, j);

		// TODO: change this brute search to a Map
		for (WingedEdge we : wingedEdges)
		{
			if (candidate.equals(we))
			{
				return we;
			}
		}

		wingedEdges.add(candidate);
		candidate.setCoveringDisk(edge.getDisk());
		candidate.setInPolygon(edge.isInPolygon());
		candidate.setInPaper(edge.isInPaper());

		if(edge.isInPaper()){
			i.setInPaper(true);
			j.setInPaper(true);
		}
		
		return candidate;
	}

	private WingedVertex findWingedVertex(Vertex v)
	{
		WingedVertex candidate = new WingedVertex(v.getX(), v.getY());

		// TODO: change this brute search to a Map
		for (WingedVertex wv : wingedVertices)
		{
			if (candidate.equals(wv))
			{
				return wv;
			}
		}
	
		wingedVertices.add(candidate);
		return candidate;
	}

	public Set<WingedFace> getFaces()
	{
		return new HashSet<WingedFace>(wingedFaces);
	}

	public Set<WingedEdge> getEdges()
	{
		return new HashSet<WingedEdge>(wingedEdges);
	}

	public Set<WingedVertex> getVertices()
	{
		return new HashSet<WingedVertex>(wingedVertices);
	}

	public void addFaces(List<WingedFace> faces)
	{
		wingedFaces.addAll(faces);
	}

	public void addEdges(List<WingedEdge> edges)
	{
		wingedEdges.addAll(edges);
	}

	public void addVertices(List<WingedVertex> vertices)
	{
		wingedVertices.addAll(vertices);
	}

	public void removeFaces(List<WingedFace> faces)
	{
		wingedFaces.removeAll(faces);
	}

	public void removeEdges(List<WingedEdge> edges)
	{
		wingedEdges.removeAll(edges);
	}

	public void removeVertices(List<WingedVertex> vertices)
	{
		wingedVertices.removeAll(vertices);
	}

	public Figure toFigure()
	{
		List<OrigamiShape> shapes = new ArrayList<OrigamiShape>();

		for (WingedEdge wingedEdge : wingedEdges)
		{
			shapes.addAll(wingedEdge.toFigure().getShapes());
		}

		for (WingedVertex wingedVertex : wingedVertices)
		{
			shapes.addAll(wingedVertex.toFigure().getShapes());
		}

		return new Figure(shapes);
	}

	public WingedFace getExternalFace()
	{

		if (exertenalFace == null)
		{
			for (WingedEdge edge : getEdges())
			{
				if (edge.getStart().equals(new WingedVertex(0, 0)) && edge.getEnd().getX() == 0)
				{
					exertenalFace = edge.getCcw();
				}
				else if (edge.getEnd().equals(new WingedVertex(0, 0)) && edge.getStart().getX() == 0)
				{
					exertenalFace = edge.getCw();
				}
			}
		}
		return exertenalFace;
	}

}
